package simulator;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


public class Simulator {
	
	private boolean graphs;
	private boolean animation;
	private int iterations;
	private int limit;
	private int netSize;
	private  ArrayList<Topology> topologies;
	
	private int k;
	GraphsUtils utils;
	GraphvizUtils graphvizUtils;
	NetUtils netUtils;
	String graphPath;
	String dotPath;

	
	public static void main(String[] args) throws InterruptedException, IOException {
		GraphsUtils utils = new GraphsUtils();
		boolean graphs = utils.getGraphs();
		boolean animation = utils.getAnimation();
		int iterations = utils.getIterations();
		int limit = utils.getLimit();
		int netSize = utils.getNetSize();
		ArrayList<Topology> topologies = new ArrayList<Topology>();
		for(Topology t: Topology.values()){
			topologies.add(t);
		}
//		topologies.add(Topology.ALL_CONNECTED);
		
		Simulator simulator = new Simulator(graphs, animation, iterations, limit, netSize, topologies);
		simulator.simulate();
	}
	
	public Simulator(boolean graphs,boolean animation, int iterations, int limit, int netsize, ArrayList<Topology> topologies) {
		this.k = 0;
		this.utils = new GraphsUtils();
		this.graphvizUtils = new GraphvizUtils();
		this.netUtils = new NetUtils();
		this.topologies = topologies;
		this.graphs = graphs;
		this.animation = animation;
		this.iterations = iterations;
		this.limit = limit;
		this.netSize = netsize;
	}

	public void simulate() {
		
		System.out.println("INFO: Starting simulation...");
	
		if(animation){
			this.removeFiles(this.utils.getGraphFolderPath());
			this.removeFiles(this.utils.getDotFolderPath());
		}
		
		for(Topology topology:	topologies){
			Net net = this.getNetByTopology(topology, netSize);
			
			HashMap<Net, HashMap<Double, List<Statistics>>> globalStats = new HashMap<Net, HashMap<Double, List<Statistics>>>();
			HashMap<Double, List<Statistics>> netStats= new HashMap<Double, List<Statistics>>();
			globalStats.put(net, netStats);
			
			if(animation){	
				File tDotDir = new File(this.utils.getDotFolderPath() + "/"+ topology);
				tDotDir.mkdir();
				File tGraphDir = new File(this.utils.getGraphFolderPath() + "/"+ topology);
				tGraphDir.mkdir();
			}
			
			while(this.utils.getAlpha() <= 1){
				System.out.println("INFO: Starting simulation with net "+topology+" and alpha = "+ this.utils.getAlpha());
				
				List<Statistics> statsList = new ArrayList<Statistics>();
				netStats.put(this.utils.getAlpha(), statsList);
			
				double P2 = this.utils.getP2();
				double P1 = this.utils.getP1();

				this.graphPath = this.utils.getGraphFolderPath() + "/"+ topology + "/alpha" + this.utils.getAlpha();
				this.dotPath = this.utils.getDotFolderPath() + "/"+ topology + "/alpha" + this.utils.getAlpha(); 
				
				if(animation){				
					File gDir = new File(this.graphPath);
					gDir.mkdir();
					File dDir = new File(this.dotPath);
					dDir.mkdir();		
					this.graphvizUtils.generateDotFile(net, this.dotPath + "/graph" + this.k);
					try {
						Runtime.getRuntime().exec("circo -T jpg -o " + this.graphPath + "/graph" + this.k + ".jpg " + this.dotPath + "/graph" + this.k + ".dot");
					} catch (IOException e) {
						e.printStackTrace();
					}
				}				
				Statistics alphaStat;
				for(int i=0; i< iterations; i++){
					if(animation){
						this.removeFiles(this.dotPath);
						this.removeFiles(this.graphPath);
					}
					alphaStat = this.simulate(P1, P2, net);
					statsList.add(alphaStat);

					//vuelvo al estado inicial del simulador
					this.restart(net);
				}
				
				//Incremento alpha
				this.utils.incrementAlpha();
			}
			//Vuelvo a alpha inicial
			this.utils.restart();
			
			if(graphs){
				BoxAndWhisker.generateGraph("Rotas", globalStats);
				BoxAndWhisker.generateGraph("Infectadas", globalStats);
				BoxAndWhisker.generateGraph("Disponibilidad", globalStats);
			}
			StatsEvaluation evaluation = new StatsEvaluation(globalStats);
			evaluation.generateResults();
		}
		System.out.println("INFO: Simulation Finished");
		
		
//		BoxAndWhisker.generateGraph("Rotas", globalStats);
//		BoxAndWhisker.generateGraph("Infectadas", globalStats);
//		BoxAndWhisker.generateGraph("Disponibilidad", globalStats);
	
	}
	
	private Statistics simulate(double P1, double P2, Net net){
		Statistics stats = new Statistics();
		while(!this.allAreSane(net) && this.k < limit) {
				for(Pc pc : net.getPcs()) {
					//Agrego en el contador de las estadisticas
					stats.add(pc.getState());
					if(pc.getStateTimeLeft() <= 0) {
						if(pc.getState().equals(State.Sane)) {
							//Si esta sana nos fijamos por cada pc conectada si se contagia o no
							for(Pc connected : pc.getConnectedPcs()) {
								//Solo las pc infectadas contagian
								if(connected.getState().equals(State.Infected)) {
									double probContagio = (1-P1)/pc.getConnectedInfectedQuantity();
									//Divido la probabilidad de q se contagie por la cantiad de pc conectadas contagiadas q tiene.
									//Porq dijimos q si la prob de contagio es 0.5 y tengo conectadas 5 infectadas, me contagio si o si.
									if(probContagio > this.utils.getRand()) {
										//p1(Prob de q no se contagie), entonces si rand > a p1 la pc se contagia.
										this.changeState(net, pc, State.Infected, this.utils.getT1(), connected);
										break;
									}
								}
							}
						} else if(pc.getState().equals(State.Infected)) {
							if(this.utils.getRand() > P2) {
								// (P2 prob de q se cure) -> si rand > P2 se rompe
								this.changeState(net, pc, State.Broken, this.utils.getT3());
							} else {
								// Si rand < P2 se cura.
								this.changeState(net, pc, State.Fixing, this.utils.getT2());
							}
						} else if(pc.getState().equals(State.Broken)) {
							//Una vez q cumplio el tiempo T3(reemplazo) pasa a estar sana
							this.changeState(net, pc, State.Sane, 0.0);
						} else if(pc.getState().equals(State.Fixing)) {
							//Una vez q cumplio el tiempo T2(de curacion) pasa a estar sana
							this.changeState(net, pc, State.Sane, 0.0);
						}
					} else {
						pc.decrementStateTimeLeft();
					}
				}
				this.k ++;
		}
		if(this.allAreSane(net)){
			System.out.println("Termino a las "+k+" horas.");
		}
		if(k < limit){
			stats.incDispTime((limit-k)*net.cantSane());
			k = limit;
		}
		
//		System.out.println("INFO: " + this.k + " graphs generated");
//		System.out.println("INFO: Graphs are located in " + this.graphPath);
//		System.out.println("INFO: Dot files are located in " + this.dotPath);
		
		return stats;
		
	}
	private void changeState(Net net, Pc pc, State state, Double time) {
		changeState(net, pc, state, time, null);
	}
	
	private void changeState(Net net, Pc pc, State state, Double time, Pc connected) {
		pc.setState(state);
		pc.setStateTimeLeft(time);
		if(animation){
			if(connected!=null){
				this.graphvizUtils.generateDotFile(net, this.dotPath + "/graph" + this.k, pc, connected);
			} else {
				this.graphvizUtils.generateDotFile(net, this.dotPath + "/graph" + this.k);
			}
			try {
				Runtime.getRuntime().exec("circo -T jpg -o " + this.graphPath + "/graph" + k + ".jpg " + this.dotPath + "/graph" + k + ".dot");
			} catch (IOException e) {
				System.out.println("ERROR: Can't create image");
			}
		}
		k++;
	}
	
	private boolean allAreSane(Net net) {
		for(Pc pc : net.getPcs()) {
			if(!pc.getState().equals(State.Sane) && !pc.getState().equals(State.Inmune)) {
				return false;
			}
		}
		return true;
	}
	
	private Net getNetByTopology(Topology topology, int size) {
		if(topology.equals(Topology.RING)) {
			return netUtils.createRingNet(size, utils.getInfectedPercentage(), utils.getT1());
		}
		if(topology.equals(Topology.ALL_CONNECTED)) {
			return netUtils.createAllConnectedNet(size, utils.getInfectedPercentage(), utils.getT1());
		}
		if(topology.equals(Topology.RANDOM)) {
			return netUtils.createRandomNet(size, utils.getInfectedPercentage(), utils.getT1());
		}
		return null;
	}
	
	private void restart(Net net){
		this.k = 0;
		this.netUtils.clearPcs(net);
		this.netUtils.infectPcs(net, utils.getInfectedPercentage(), utils.getT1());
	}
	
	private void removeFiles(String path){
		File dir = new File(path);
		for(File f: dir.listFiles()) {
			removeFile(f);
		}
	}
	
	private void removeFile(File f){
		if(f.isDirectory()){
			for(File h: f.listFiles()) {
				removeFile(h);
			}
		}
		f.delete();
	}
}
